/*	talmud - The TALMUD roguelike
 * Copyright (C) 2012, talmud contributors
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of Version 2 of the GNU General Public License as published
 *  by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE. See Version 2 of the GNU General Public
 *  License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>
#include "talmud.h"
#include "monster.h"
#include "race.h"
#include "class.h"
#include "playermanage.h"
#include "draw.h"
#include "monstermanage.h"
#include "config.h"
#include "rand.h"
#include "itemmanage.h"

#define MSGMAX		100
#define MSGMAXLEN	240
#define EXTCMD_MAXLEN	32

config_t cfg;
player_t plyr;
dlvl_t dlvls[BRANCHES][MAXDEPTH];

const branchdef_t BRANCHDEFS[BRANCHES] = {
	{
		"The Dungeon",
		DUNGEON_DEPTH,
	},
	{
		"The Catacombs of Gideon",
		CATACOMBS_DEPTH,
	}
};

uint32_t turncount;

static uint8_t gameover;
static char msgs[MSGMAX][MSGMAXLEN];
static int msgcount;
static const char *name;

char *capword(const char *word)
{
	static char cap[256];

	strcpy(cap, word);
	cap[0] = toupper(cap[0]);

	return cap;
}

void GameOver(void)
{
	gameover = 1;
}

void Unimp(const char *error)
{
	AddMsg("You stumble across an unimplemented feature in %s. It explodes! You die.", error);
	GameOver();
}

void Bug(const char *error)
{
	AddMsg("You find a bug in %s. It explodes! You die.", error);
	GameOver();
}

void AddMsg(const char *format, ...)
{
	va_list ap;
	if (msgcount >= MSGMAX)
	{
		return;
	}
	va_start(ap, format);
	vsnprintf(msgs[msgcount], MSGMAXLEN, format, ap);
	va_end(ap);
	msgcount++;
}

#define RNT_ITERATIONS	1000000
#define RNT_BUCKETS	10

static void rntest(void)
{
	uint32_t iter;
	uint8_t roll;
	uint32_t buckets[RNT_BUCKETS];
	uint8_t idx;

	printf("Testing rn1\n");
	memset(buckets, 0, sizeof(uint32_t) * RNT_BUCKETS);
	for (iter = 0; iter < RNT_ITERATIONS; iter++)
	{
		roll = rn1(RNT_BUCKETS);
		buckets[roll]++;
	}
	for (idx = 0; idx < RNT_BUCKETS; idx++)
	{
		printf("\t%i: %i\t=\t%.4f\n", idx, buckets[idx], buckets[idx] / (float)RNT_ITERATIONS);
	}

	printf("Testing rn2\n");
	memset(buckets, 0, sizeof(uint32_t) * RNT_BUCKETS);
	for (iter = 0; iter < RNT_ITERATIONS; iter++)
	{
		roll = rn2(RNT_BUCKETS);
		buckets[roll]++;
	}
	for (idx = 0; idx < RNT_BUCKETS; idx++)
	{
		printf("\t%i: %i\t=\t%.4f\n", idx, buckets[idx], buckets[idx] / (float)RNT_ITERATIONS);
	}

	printf("Testing rn3\n");
	memset(buckets, 0, sizeof(uint32_t) * RNT_BUCKETS);
	for (iter = 0; iter < RNT_ITERATIONS; iter++)
	{
		roll = rn3(RNT_BUCKETS);
		buckets[roll]++;
	}
	for (idx = 0; idx < RNT_BUCKETS; idx++)
	{
		printf("\t%i: %i\t=\t%.4f\n", idx, buckets[idx], buckets[idx] / (float)RNT_ITERATIONS);
	}
}

static uint8_t PickInv(const char *title)
{
	uint8_t idx;
	uint8_t longestlen;
	uint8_t len;
	uint8_t pages;
	uint8_t currentpage;
	uint8_t y;
	uint8_t lasttype = 0xFF;
	int c;

	pages = invcount / 20;
	currentpage = 0;
	do
	{
		InitDraw();
		SetColor(A_WHITE);
		DrawCenteredStr(40, 0, "-%s (page %i of %i)-", title, currentpage + 1, pages + 1);
		longestlen = 0;
		for (idx = currentpage * 20; idx < (currentpage + 1) * 20 && idx < invcount; idx++)
		{
			len = strlen(ItemDesc(&inv[idx], idx)) + 4;
			if (len > longestlen)
			{
				longestlen = len;
			}
		}
		y = 0;
		for (idx = currentpage * 20; idx < (currentpage + 1) * 20 && idx < invcount; idx++)
		{
			if (ITEMDEFS[inv[idx].type].type != lasttype)
			{
				SetColor(A_BLACK | A_BGWHITE);
				lasttype = ITEMDEFS[inv[idx].type].type;
				DrawStr(40 - (longestlen >> 1), y + 2, "%s", ITEMTYPESTRS[lasttype]);
				y++;
				SetColor(A_WHITE);
			}
			DrawStr(40 - (longestlen >> 1), y + 2, "%c - %s", invchar[idx], ItemDesc(&inv[idx], idx));
			y++;
		}
		PutCursor(0, 0);
		FlushDraw();
		while (1)
		{
			c = getchar();
			if (c == ' ')
			{
				break;
			}
			for (idx = 0; idx < invcount; idx++)
			{
				if (c == invchar[idx])
				{
					return idx;
				}
			}
		}
		currentpage++;
	} while (currentpage < pages);

	return INV_NONE;
}

void Inv(void)
{
	uint8_t idx;
	uint8_t longestlen;
	uint8_t len;
	uint8_t pages;
	uint8_t currentpage;
	uint8_t y;
	uint8_t lasttype = 0xFF;
	char itemc;
	int c;

	if (!invcount)
	{
		return;
	}

	pages = invcount / 20;
	currentpage = 0;

	do
	{
		InitDraw();
		SetColor(A_WHITE);
		DrawCenteredStr(40, 0, "-Your Inventory (page %i of %i)-", currentpage + 1, pages + 1);
		longestlen = 0;
		for (idx = currentpage * 20; idx < (currentpage + 1) * 20 && idx < invcount; idx++)
		{
			len = strlen(ItemDesc(&inv[idx], idx)) + 4;
			if (len > longestlen)
			{
				longestlen = len;
			}
		}
		y = 0;
		for (idx = currentpage * 20; idx < (currentpage + 1) * 20 && idx < invcount; idx++)
		{
			if (ITEMDEFS[inv[idx].type].type != lasttype)
			{
				SetColor(A_BLACK | A_BGWHITE);
				lasttype = ITEMDEFS[inv[idx].type].type;
				DrawStr(40 - (longestlen >> 1), y + 2, "%s", ITEMTYPESTRS[lasttype]);
				y++;
				SetColor(A_WHITE);
			}
			itemc = invchar[idx];
			DrawStr(40 - (longestlen >> 1), y + 2, "%c - %s", itemc, ItemDesc(&inv[idx], idx));
			y++;
		}
		PutCursor(0, 0);
		FlushDraw();
		do
		{
			c = getchar();
		} while (c != ' ');
		currentpage++;
	} while (currentpage < pages);
}

uint8_t Pickup(void)
{
	uint8_t *itemids;
	uint16_t itemcount;
	uint8_t idx;
	uint8_t longestlen;
	uint8_t len;
	uint8_t pages;
	uint8_t currentpage;
	uint8_t y;
	uint8_t onpage;
	int c;

	itemcount = GetItemsAt(plyrmon.x, plyrmon.y, &itemids);
	if (!itemcount)
	{
		AddMsg("You see nothing here to pick up.");
		return 0;
	}
	else if (itemcount == 1)
	{
		PlyrPickup(itemids[0]);
		free(itemids);
		return 1;
	}

	pages = itemcount / 20;
	currentpage = 0;
	do
	{
		InitDraw();
		SetColor(A_WHITE);
		DrawCenteredStr(40, 0, "-You see here... (page %i of %i)-", currentpage + 1, pages + 1);
		longestlen = 0;
		for (idx = currentpage * 20; idx < (currentpage + 1) * 20 && idx < itemcount; idx++)
		{
			len = strlen(ItemDesc(&items[itemids[idx]], INV_NONE)) + 4;
			if (len > longestlen)
			{
				longestlen = len;
			}
		}
		y = 0;
		for (idx = currentpage * 20; idx < (currentpage + 1) * 20 && idx < itemcount; idx++)
		{
			DrawStr(40 - (longestlen >> 1), y + 2, "%c - %s", 'a' + idx, ItemDesc(&items[itemids[idx]], INV_NONE));
			y++;
		}
		onpage = idx - (currentpage * 20);
		PutCursor(0, 0);
		FlushDraw();
		while (1)
		{
			c = getchar();
			if (c == ' ')
			{
				break;
			}
			else if (c >= 'a' && c <= 'a' + (onpage - 1))
			{
				PlyrPickup(itemids[(currentpage * 20) + (c - 'a')]);
				free(itemids);
				return 1;
			}
		}
		currentpage++;
	} while (currentpage < pages);
	free(itemids);

	return 0;
}

uint8_t Drop(void)
{
	uint8_t invidx;

	invidx = PickInv("Drop what?");
	if (invidx != INV_NONE)
	{
		PlyrDrop(invidx);
		return 1;
	}

	return 0;
}

uint8_t Zap(void)
{
	return 0;
}

uint8_t Quaff(void)
{
	uint8_t invidx;

	invidx = PickInv("What do you want to drink?");
	if (invidx != INV_NONE)
	{
		return PlyrQuaff(invidx);
	}

	return 0;
}

uint8_t Wield(void)
{
	uint8_t invidx;

	invidx = PickInv("What do you want to wield?");
	if (invidx != INV_NONE)
	{
		return PlyrWield(invidx);
	}

	return 0;
}

uint8_t Wear(void)
{
	uint8_t invidx;

	invidx = PickInv("What do you want to wear?");
	if (invidx != INV_NONE)
	{
		return PlyrWear(invidx);
	}

	return 0;
}

uint8_t Takeoff(void)
{
	uint8_t invidx;

	invidx = PickInv("What do you want to take off?");
	if (invidx != INV_NONE)
	{
		return PlyrTakeoff(invidx);
	}

	return 0;
}

void HandlePrompt(const char *msg)
{
	const char *prompt;
	char input[64];
	int c;
	uint8_t itype;

	prompt = strstr(&msg[1], "$") + 1;
	strcpy(input, "");
	do
	{
		Cll(0, 0);
		DrawStr(0, 0, "%s %s", prompt, input);
		FlushDraw();
		c = getchar();
		if (c == '\b')
		{
			if (strlen(input) > 0)
			{
				input[strlen(input) - 1] = '\0';
			}
		}
		else if (c == '\r' || c == '\n')
		{
			break;
		}
		else
		{
			if (c == '\t')
			{
				c = ' ';
			}
			if (strlen(input) < 64)
			{
				input[strlen(input) + 1] = '\0';
				input[strlen(input)] = c;
			}
		}
	} while (1);

	if (strstr(msg, "$PROMPT:ITYPEDESC:") == msg)
	{
		sscanf(msg, "$PROMPT:ITYPEDESC:%hhi", &itype);
		strncpy(itypedesc[itype], input, 63);
		itypedesc[itype][63] = '\0';
	}
}

int main(int argc, char *argv[])
{
	int c;
	struct termios termattr;
	char extcmd[EXTCMD_MAXLEN];
	uint8_t x;
	uint8_t idx;
	uint8_t turnpasses;
	uint8_t delay;

	if (argc <= 1)
	{
		srand(time(NULL));
	}
	else
	{
		if (!strcmp(argv[1], "rntest"))
		{
			srand(time(NULL));
			rntest();
			return 1;
		}
		srand(atoi(argv[1]));
	}

	name = getenv("USER");
	if (!name)
	{
		printf("No USER\n");
		return 1;
	}

	SetupDefaultConfig();
	LoadConfig();

	tcgetattr(STDIN_FILENO, &termattr);
	termattr.c_lflag &= ~(ICANON | ECHO);
	tcsetattr(STDIN_FILENO, TCSANOW, &termattr);

	Cls();
	msgcount = 0;
	InitItemDescs();
	InitPlayer(R_HUMAN, C_MAGEWEAVER);
	BuildPlayer();
	plyrmon.dlvl = 1;
	plyrmon.branch = B_DUNGEONS;
	PlayerLevels();
	PlayerEnters(0);
	gameover = 0;
	turnpasses = 0;
	turncount = 0;
	UpdatePlyr();
	while (!gameover)
	{
		delay = 0;
		if (turnpasses)
		{
			if (plyrmon.cintrins & IM(I_INVIS))
			{
				plyr.invisturns--;
				if (!plyr.invisturns)
				{
					plyrmon.cintrins &= ~IM(I_INVIS);
				}
			}
			if (plyrmon.cintrins & IM(I_INFRAVIS))
			{
				plyr.infravisturns--;
				if (!plyr.infravisturns)
				{
					plyrmon.cintrins &= ~IM(I_INFRAVIS);
				}
			}
			if (plyrmon.cintrins & IM(I_PARALYSIS))
			{
				delay = 1;
				plyr.paralyzedturns--;
				if (!plyr.paralyzedturns)
				{
					plyrmon.cintrins &= ~IM(I_PARALYSIS);
				}
			}
			if (plyrmon.cintrins & IM(I_SLEEP))
			{
				delay = 1;
				plyr.sleepturns--;
				if (!plyr.sleepturns)
				{
					plyrmon.cintrins &= ~IM(I_SLEEP);
				}
			}
			if (plyrmon.cintrins & IM(I_CONFUSION))
			{
				plyr.confturns--;
				if (!plyr.confturns)
				{
					plyrmon.cintrins &= ~IM(I_CONFUSION);
				}
			}
			plyrmon.speedcounter -= TURNSPEED;
			turncount++;
		}
		if (delay)
		{
			usleep(250000);
		}
		do
		{
			if (turnpasses)
			{
				CullDeadMon();
				UpdatePlyr();
				UpdateMon();
				CullDeadMon();
			}

			InitDraw();
			Cll(0, 22);
			SetColor(A_WHITE);
			DrawStr(0, 22, "%s the level %i %s | St:%i Dx:%i Co:%i In:%i Wi:%i Ch:%i", name, plyr.xlvl, CLASSDEFS[plyr.class], plyr.st, plyr.dx, plyr.co, plyr.in, plyr.wi, plyr.ch);
			Cll(0, 23);
			x = DrawStr(0, 23, "Dlvl:%i Xl:%i/%i Hp:", plyrmon.dlvl, plyr.xlvl, plyr.xp);
			if (plyrmon.hp <= plyr.maxhp / 2)
			{
				SetColor(A_RED);
			}
			else if (plyrmon.hp <= plyr.maxhp * 3 / 4)
			{
				SetColor(A_INTENSE | A_YELLOW);
			}
			else
			{
				SetColor(A_GREEN);
			}
			x += DrawStr(x, 23, "%i", plyrmon.hp);
			SetColor(A_WHITE);
			x += DrawStr(x, 23, "/");
			SetColor(A_GREEN);
			x += DrawStr(x, 23, "%i ", plyr.maxhp);
			SetColor(A_WHITE);
			x += DrawStr(x, 23, "AC:%i T:%i", plyr.ac, turncount);
			for (idx = I_NONE + 1; idx < I_INTRINSCOUNT; idx++)
			{
				if (idx == I_INFRAVIS)
				{
					continue;
				}
				if (plyrmon.cintrins & IM(idx))
				{
					x += DrawStr(x, 23, " %s", INTRINSNAME[idx]);
				}
			}
			ExposeLevel();
			DrawLevel();
			DrawItems();
			DrawMon();
			DrawPlyr();
			if (!turnpasses)
			{
				break;
			}
		} while (plyrmon.speedcounter < TURNSPEED);

		SetColor(A_WHITE);
		while (msgcount > 0)
		{
			if (msgs[0][0] == '$')
			{
				HandlePrompt(msgs[0]);
				for (idx = 0; idx < msgcount - 1; idx++)
				{
					strcpy(msgs[idx], msgs[idx + 1]);
				}
				msgcount--;
			}
			if (msgcount > 1)
			{
				Cll(0, 0);
				DrawStr(0, 0, "%s -More-", msgs[0]);
				FlushDraw();
				do
				{
					c = getchar();
				} while (c != ' ');
				for (idx = 0; idx < msgcount - 1; idx++)
				{
					strcpy(msgs[idx], msgs[idx + 1]);
				}
				msgcount--;
			}
			else if (msgcount == 1)
			{
				Cll(0, 0);
				DrawStr(0, 0, "%s", msgs[0]);
				FlushDraw();
				msgcount--;
			}
		}

		strcpy(extcmd, "");
		while (!gameover)
		{
			if (extcmd[0] == '#')
			{
				SetColor(A_WHITE);
				Cll(0, 0);
				DrawStr(0, 0, "# %s", &extcmd[1]);
			}
			else
			{
				PutCursor(plyrmon.x, plyrmon.y + DLVL_BASEY);
			}
			FlushDraw();

			if (plyrmon.cintrins & IM(I_PARALYSIS) || plyrmon.cintrins & IM(I_SLEEP))
			{
				c = '.';
			}
			else
			{
				c = getchar();
			}
			if (extcmd[0] == '#' || c == '#')
			{
				if (strlen(extcmd) >= EXTCMD_MAXLEN)
				{
					continue;
				}
				if (c == '\r' || c == '\n')
				{
					break;
				}
				else if (c == '\b' || c == 0x1b || c == 0x7f)
				{
					if (strlen(extcmd) > 0)
					{
						extcmd[strlen(extcmd) - 1] = '\0';
					}
					if (strlen(extcmd) <= 1)
					{
						strcpy(extcmd, "");
					}
				}
				else
				{
					extcmd[strlen(extcmd) + 1] = '\0';
					extcmd[strlen(extcmd)] = c;
				}
			}
			else
			{
				break;
			}
		}
		FlushDraw();
		turnpasses = 1;
		if (!strcmp(extcmd, "#quit"))
		{
			GameOver();
		}
		else if (!strcmp(extcmd, ""))
		{
			if (c == inkey[K_MOVE1])
			{
				turnpasses = MovePlyr(-1, 1);
			}
			else if (c == inkey[K_MOVE2])
			{
				turnpasses = MovePlyr(0, 1);
			}
			else if (c == inkey[K_MOVE3])
			{
				turnpasses = MovePlyr(1, 1);
			}
			else if (c == inkey[K_MOVE4])
			{
				turnpasses = MovePlyr(-1, 0);
			}
			else if (c == inkey[K_MOVE6])
			{
				turnpasses = MovePlyr(1, 0);
			}
			else if (c == inkey[K_MOVE7])
			{
				turnpasses = MovePlyr(-1, -1);
			}
			else if (c == inkey[K_MOVE8])
			{
				turnpasses = MovePlyr(0, -1);
			}
			else if (c == inkey[K_MOVE9])
			{
				turnpasses = MovePlyr(1, -1);
			}
			else if (c == inkey[K_MOVEUP])
			{
				turnpasses = GoUp();
			}
			else if (c == inkey[K_MOVEDN])
			{
				turnpasses = GoDn();
			}
			else if (c == inkey[K_INV])
			{
				turnpasses = 0;
				Inv();
			}
			else if (c == inkey[K_WAIT])
			{
			}
			else if (c == inkey[K_PICKUP])
			{
				turnpasses = Pickup();
			}
			else if (c == inkey[K_DROP])
			{
				turnpasses = Drop();
			}
			else if (c == inkey[K_ZAP])
			{
				turnpasses = Zap();
			}
			else if (c == inkey[K_QUAFF])
			{
				turnpasses = Quaff();
			}
			else if (c == inkey[K_WIELD])
			{
				turnpasses = Wield();
			}
			else if (c == inkey[K_WEAR])
			{
				turnpasses = Wear();
			}
			else if (c == inkey[K_TAKEOFF])
			{
				turnpasses = Takeoff();
			}
			else
			{
				turnpasses = 0;
			}
		}
	}

	termattr.c_lflag |= (ICANON | ECHO);
	tcsetattr(STDIN_FILENO, TCSANOW, &termattr);

	return 0;
}
